package org.angelica.core.aop;

import lombok.extern.slf4j.Slf4j;
import org.angelica.core.common.Constant;
import org.angelica.core.common.HttpLog;
import org.angelica.utils.common.SpringContextUtil;
import org.angelica.utils.common.ToolUtil;
import org.angelica.utils.http.HttpUtil;
import org.angelica.utils.id.IdGeneratorUtil;
import org.angelica.utils.json.JacksonUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 统一访问日志
 * @author aiken
 */
@Aspect
@Component
@Order(1)
@Slf4j
public class HttpLogAspect {

	/**
	 * 请求唯一ID
	 */
	private final static String REQUEST_ID = "requestId";

	/**
	 * ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
	 * 当使用ThreadLocal维护变量时，ThreadLocal为每个使用该变量的线程提供独立的变量副本，所以每一个线程都可以独立地改变自己的副本，而不会影响其它线程所对应的副本。
	 */
	ThreadLocal<HttpLog> httpLogThreadLocal = new ThreadLocal<HttpLog>();

	@Pointcut("execution(* org.angelica.modules.controller..*.*(..))")
	public void log() { }

	/**
	 * 记录请求日志
	 * @param joinPoint 切面
	 */
	@Before("log()")
	public void doBefore(JoinPoint joinPoint) {
		MDC.put(REQUEST_ID, IdGeneratorUtil.getRandomUuid());

		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();

		HttpLog httpLog = HttpLog.builder()
				.appName(SpringContextUtil.getApplicationName())
				.activeProfile(SpringContextUtil.getActiveProfile())
				.clientIp(HttpUtil.getIpAddr(request))
				.httpUrl(request.getRequestURL().toString())
				.httpMethod(request.getMethod())
				.classMethod(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName())
				.currentUser((Long)request.getAttribute(Constant.CURRENT_USER_ID))
				.params(JacksonUtil.nonDefaultMapper().toJson(HttpUtil.getParamMap(request)))
				.headers(JacksonUtil.nonDefaultMapper().toJson(HttpUtil.getHeaderMap(request)))
				.startTime(System.currentTimeMillis())
				.build();
		httpLogThreadLocal.set(httpLog);
	}
	
	
	@After("log()")
	public void doAfter() {}
	
	/**
	 * 目标方法返回时调用
	 * @param object 返回对象
	 */
	@AfterReturning(value="log()",returning="object")
	public void doAfterReturning(Object object) {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletResponse response = attributes.getResponse();
		//获取请求返回码
		int status = response.getStatus();
		//获取本次请求日志对象
		HttpLog httpLog = httpLogThreadLocal.get();
		//请求结束时间
		httpLog.setEndTime(System.currentTimeMillis());
		//设置请求时长
		httpLog.setDuration((int)(httpLog.getEndTime() - httpLog.getStartTime()));
		//设置返回错误码
		httpLog.setHttpStatus(status);
		//设置返回值
		String responseJson = JacksonUtil.nonDefaultMapper().toJson(object);
		if(StringUtils.isNoneBlank(responseJson) && responseJson.length() > 1000){
			responseJson = responseJson.substring(0,1000);
		}
		httpLog.setResponse(responseJson);
		//记录日志
		log.info(JacksonUtil.nonEmptyMapper().toJson(httpLog));

		//移除mdc
		MDC.remove(REQUEST_ID);

		httpLogThreadLocal.remove();
	}

	/**
	 * 目标方法出现异常调用
	 * @param joinPoint
	 * @param exception
	 */
	@AfterThrowing(value = "log()",throwing = "exception")
	public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){
		//获取本次请求日志对象
		HttpLog httpLog = httpLogThreadLocal.get();
		//获取请求错误码
		httpLog.setHttpStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
		//请求结束时间
		httpLog.setEndTime(System.currentTimeMillis());
		//请求方法中的异常信息
		httpLog.setException(ToolUtil.getExceptionMsg(exception));

		//记录日志
		log.info(JacksonUtil.nonEmptyMapper().toJson(httpLog));

		//移除mdc
		MDC.remove(REQUEST_ID);

		httpLogThreadLocal.remove();
	}

}
